React์ experimental_useFormState๋ฅผ ํ์ฉํ ๊ณ ๊ธ ํผ ์ ํจ์ฑ ๊ฒ์ฌ์ ๋ํด ์์๋ณด์ธ์. ์ด ๊ฐ์ด๋์์๋ ๊ตฌํ ๋ฐฉ๋ฒ, ์ด์ , ๊ทธ๋ฆฌ๊ณ ์ค์ ์ฌ์ฉ ์์ ๋ฅผ ๋ค๋ฃน๋๋ค.
React experimental_useFormState ์ ํจ์ฑ ๊ฒ์ฌ: ํฅ์๋ ํผ ๊ฒ์ฆ
ํผ ์ ํจ์ฑ ๊ฒ์ฌ๋ ํ๋ ์น ์ ํ๋ฆฌ์ผ์ด์
๊ฐ๋ฐ์ ํต์ฌ์ ์ธ ๋ถ๋ถ์
๋๋ค. ์ด๋ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ์ ๋ณด์ฅํ๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํค๋ฉฐ, ์์คํ
์ ์ฒด์ ์ค๋ฅ๊ฐ ์ ํ๋๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค. ์ปดํฌ๋ํธ ๊ธฐ๋ฐ ์ํคํ
์ฒ๋ฅผ ๊ฐ์ง React๋ ํผ ์ฒ๋ฆฌ ๋ฐ ์ ํจ์ฑ ๊ฒ์ฌ์ ๋ํ ์๋ง์ ์ ๊ทผ ๋ฐฉ์์ ์ ๊ณตํฉ๋๋ค. React์ ์คํ์ ๊ธฐ๋ฅ์ผ๋ก ๋์
๋ experimental_useFormState ํ
์ ์๋ฒ ์ก์
๋ด์์ ์ง์ ํผ ์ํ์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ๊ด๋ฆฌํ๋ ๊ฐ๋ ฅํ๊ณ ๊ฐ์ํ๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํ์ฌ, ์ ์ง์ ํฅ์๊ณผ ๋ ๋ถ๋๋ฌ์ด ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
experimental_useFormState ์ดํดํ๊ธฐ
experimental_useFormState ํ
์ ํนํ ์๋ฒ ์ก์
๊ณผ ์ํธ์์ฉํ ๋ ํผ ์ํ ๊ด๋ฆฌ ๊ณผ์ ์ ๋จ์ํํ๋๋ก ์ค๊ณ๋์์ต๋๋ค. ๋ ๋ค๋ฅธ ์คํ์ ๊ธฐ๋ฅ์ธ ์๋ฒ ์ก์
์ ์๋ฒ์์ ์ ์ํ ํจ์๋ฅผ React ์ปดํฌ๋ํธ์์ ์ง์ ํธ์ถํ ์ ์๊ฒ ํด์ค๋๋ค. experimental_useFormState๋ ์๋ฒ ์ก์
์ ๊ฒฐ๊ณผ์ ๋ฐ๋ผ ํผ ์ํ๋ฅผ ์
๋ฐ์ดํธํ๋ ๋ฉ์ปค๋์ฆ์ ์ ๊ณตํ์ฌ ์ค์๊ฐ ์ ํจ์ฑ ๊ฒ์ฌ์ ํผ๋๋ฐฑ์ ์ฉ์ดํ๊ฒ ํฉ๋๋ค.
์ฃผ์ ์ด์ :
- ๋จ์ํ๋ ํผ ๊ด๋ฆฌ: ํผ ์ํ์ ์ ํจ์ฑ ๊ฒ์ฌ ๋ก์ง์ ์ปดํฌ๋ํธ ๋ด์์ ์ค์ ์ง์คํํฉ๋๋ค.
- ์๋ฒ ์ธก ์ ํจ์ฑ ๊ฒ์ฌ: ์๋ฒ์์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํํ์ฌ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ๊ณผ ๋ณด์์ ๋ณด์ฅํฉ๋๋ค.
- ์ ์ง์ ํฅ์: ์๋ฐ์คํฌ๋ฆฝํธ๊ฐ ๋นํ์ฑํ๋ ๊ฒฝ์ฐ์๋ ์ํํ๊ฒ ์๋ํ์ฌ ๊ธฐ๋ณธ์ ์ธ ํผ ์ ์ถ ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค.
- ์ค์๊ฐ ํผ๋๋ฐฑ: ์ ํจ์ฑ ๊ฒ์ฌ ๊ฒฐ๊ณผ์ ๋ฐ๋ผ ์ฌ์ฉ์์๊ฒ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ ์ ๊ณตํฉ๋๋ค.
- ๋ณด์ผ๋ฌํ๋ ์ดํธ ๊ฐ์: ํผ ์ํ ๋ฐ ์ ํจ์ฑ ๊ฒ์ฌ ์ฒ๋ฆฌ์ ํ์ํ ์ฝ๋์ ์์ ์ต์ํํฉ๋๋ค.
experimental_useFormState ๊ตฌํํ๊ธฐ
experimental_useFormState๋ฅผ ๊ตฌํํ๋ ์ค์ฉ์ ์ธ ์์ ๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ๊ฐ๋จํ ์ ํจ์ฑ ๊ฒ์ฌ ๊ท์น(์: ํ์ ํ๋, ์ด๋ฉ์ผ ํ์)์ ๊ฐ์ง ํ์๊ฐ์
ํผ์ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค. ์ด ์์ ๋ ํ
์ ์๋ฒ ์ก์
๊ณผ ํตํฉํ์ฌ ํผ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฆํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค ๊ฒ์
๋๋ค.
์์ : ํ์๊ฐ์ ํผ
๋จผ์ , ํผ ์ ์ถ ๋ฐ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ฒ๋ฆฌํ ์๋ฒ ์ก์ ์ ์ ์ํด ๋ณด๊ฒ ์ต๋๋ค. ์ด ์ก์ ์ ํผ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ ํจ์ฑ ๊ฒ์ฌ์ ์คํจํ๋ฉด ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋ฐํํฉ๋๋ค.
// server-actions.js (์ด๊ฒ์ ํํ์ผ ๋ฟ์
๋๋ค. ์๋ฒ ์ก์
์ ์ ํํ ๊ตฌํ์ ํ๋ ์์ํฌ์ ๋ฐ๋ผ ๋ค๋ฆ
๋๋ค.)
"use server";
export async function registerUser(prevState, formData) {
const name = formData.get('name');
const email = formData.get('email');
const password = formData.get('password');
// ๊ฐ๋จํ ์ ํจ์ฑ ๊ฒ์ฌ
if (!name) {
return { message: '์ด๋ฆ์ ํ์์
๋๋ค' };
}
if (!email || !email.includes('@')) {
return { message: '์๋ชป๋ ์ด๋ฉ์ผ ํ์์
๋๋ค' };
}
if (!password || password.length < 8) {
return { message: '๋น๋ฐ๋ฒํธ๋ 8์ ์ด์์ด์ด์ผ ํฉ๋๋ค' };
}
// ์ฌ์ฉ์ ๋ฑ๋ก ์๋ฎฌ๋ ์ด์
await new Promise(resolve => setTimeout(resolve, 1000)); // API ํธ์ถ ์๋ฎฌ๋ ์ด์
return { message: 'ํ์๊ฐ์
์ฑ๊ณต!' };
}
์ด์ experimental_useFormState๋ฅผ ์ฌ์ฉํ์ฌ ํผ์ ๊ด๋ฆฌํ๊ณ ์๋ฒ ์ก์
๊ณผ ์ํธ์์ฉํ๋ React ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค.
// RegistrationForm.jsx
'use client';
import React from 'react';
import { experimental_useFormState as useFormState } from 'react-dom';
import { registerUser } from './server-actions';
function RegistrationForm() {
const [state, formAction] = useFormState(registerUser, { message: null });
return (
);
}
export default RegistrationForm;
์ค๋ช :
experimental_useFormState์registerUser์๋ฒ ์ก์ ์ ๊ฐ์ ธ์ต๋๋ค.useFormState(registerUser, { message: null })๋ ํ ์ ์ด๊ธฐํํฉ๋๋ค. ์ฒซ ๋ฒ์งธ ์ธ์๋ ์๋ฒ ์ก์ ์ด๊ณ , ๋ ๋ฒ์งธ๋ ์ด๊ธฐ ์ํ์ ๋๋ค. ์ด ๊ฒฝ์ฐ ์ด๊ธฐ ์ํ๋message์์ฑ์ดnull๋ก ์ค์ ๋์ด ์์ต๋๋ค.- ์ด ํ
์ ํ์ฌ ์ํ(
state)์ ์๋ฒ ์ก์ ์ ํธ๋ฆฌ๊ฑฐํ๋ ํจ์(formAction)๋ฅผ ํฌํจํ๋ ๋ฐฐ์ด์ ๋ฐํํฉ๋๋ค. <form>์์์action์์ฑ์formAction์ผ๋ก ์ค์ ๋ฉ๋๋ค. ์ด๋ React์๊ฒ ํผ์ด ์ ์ถ๋ ๋ ์๋ฒ ์ก์ ์ ์ฌ์ฉํ๋๋ก ์ง์ํฉ๋๋ค.state?.message๋ ์๋ฒ ์ก์ ์์ ๋ฐํ๋ ์ค๋ฅ ๋ฉ์์ง๋ ์ฑ๊ณต ๋ฉ์์ง๋ฅผ ํ์ํ๊ธฐ ์ํด ์กฐ๊ฑด๋ถ๋ก ๋ ๋๋ง๋ฉ๋๋ค.
๊ณ ๊ธ ์ ํจ์ฑ ๊ฒ์ฌ ๊ธฐ๋ฒ
์ด์ ์์ ๋ ๊ธฐ๋ณธ์ ์ธ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ๋ณด์ฌ์ฃผ์์ง๋ง, ๋ ์ ๊ตํ ๊ฒ์ฌ ๊ธฐ๋ฒ์ ํตํฉํ ์ ์์ต๋๋ค. ๋ค์์ ๋ช ๊ฐ์ง ๊ณ ๊ธ ์ ๋ต์ ๋๋ค:
- ์ ๊ท ํํ์: ์ ํ๋ฒํธ, ์ฐํธ๋ฒํธ, ์ ์ฉ์นด๋ ๋ฒํธ์ ๊ฐ์ ๋ณต์กํ ํจํด์ ๊ฒ์ฆํ๊ธฐ ์ํด ์ ๊ท ํํ์์ ์ฌ์ฉํฉ๋๋ค. ๋ฐ์ดํฐ ํ์์ ๋ฌธํ์ ์ฐจ์ด(์: ๊ตญ๊ฐ๋ง๋ค ์ ํ๋ฒํธ ํ์์ด ํฌ๊ฒ ๋ค๋ฆ)๋ฅผ ๊ณ ๋ คํ์ธ์.
- ์ฌ์ฉ์ ์ ์ ์ ํจ์ฑ ๊ฒ์ฌ ํจ์: ๋ ๋ณต์กํ ์ ํจ์ฑ ๊ฒ์ฌ ๋ก์ง์ ๊ตฌํํ๊ธฐ ์ํด ์ฌ์ฉ์ ์ ์ ํจ์๋ฅผ ๋ง๋ญ๋๋ค. ์๋ฅผ ๋ค์ด, ์ฌ์ฉ์ ์ด๋ฆ์ด ์ด๋ฏธ ์ฌ์ฉ ์ค์ธ์ง ํ์ธํ๊ฑฐ๋ ๋น๋ฐ๋ฒํธ๊ฐ ํน์ ๊ธฐ์ค(์: ์ต์ ๊ธธ์ด, ํน์ ๋ฌธ์)์ ์ถฉ์กฑํ๋์ง ํ์ธํด์ผ ํ ์ ์์ต๋๋ค.
- ์๋ํํฐ ์ ํจ์ฑ ๊ฒ์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ: ๋ ๊ฐ๋ ฅํ๊ณ ๊ธฐ๋ฅ์ด ํ๋ถํ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ํด Zod, Yup, Joi์ ๊ฐ์ ์๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํ์ธ์. ์ด๋ฌํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ข ์ข ์คํค๋ง ๊ธฐ๋ฐ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ ๊ณตํ์ฌ ๊ฒ์ฌ ๊ท์น์ ์ ์ํ๊ณ ์ํํ๊ธฐ ์ฝ๊ฒ ๋ง๋ญ๋๋ค.
์์ : Zod๋ฅผ ์ฌ์ฉํ ์ ํจ์ฑ ๊ฒ์ฌ
Zod๋ ์ธ๊ธฐ ์๋ TypeScript ์ฐ์ ์คํค๋ง ์ ์ธ ๋ฐ ์ ํจ์ฑ ๊ฒ์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค. Zod๋ฅผ ์ฐ๋ฆฌ์ ํ์๊ฐ์ ํผ ์์ ์ ํตํฉํด ๋ณด๊ฒ ์ต๋๋ค.
// server-actions.js
"use server";
import { z } from 'zod';
const registrationSchema = z.object({
name: z.string().min(2, { message: "์ด๋ฆ์ 2์ ์ด์์ด์ด์ผ ํฉ๋๋ค." }),
email: z.string().email({ message: "์๋ชป๋ ์ด๋ฉ์ผ ์ฃผ์์
๋๋ค" }),
password: z.string().min(8, { message: "๋น๋ฐ๋ฒํธ๋ 8์ ์ด์์ด์ด์ผ ํฉ๋๋ค." }),
});
export async function registerUser(prevState, formData) {
const data = Object.fromEntries(formData);
try {
const validatedData = registrationSchema.parse(data);
// ์ฌ์ฉ์ ๋ฑ๋ก ์๋ฎฌ๋ ์ด์
await new Promise(resolve => setTimeout(resolve, 1000)); // API ํธ์ถ ์๋ฎฌ๋ ์ด์
return { message: 'ํ์๊ฐ์
์ฑ๊ณต!' };
} catch (error) {
if (error instanceof z.ZodError) {
return { message: error.errors[0].message };
} else {
return { message: '์์์น ๋ชปํ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.' };
}
}
}
์ค๋ช :
zod๋ผ์ด๋ธ๋ฌ๋ฆฌ์์z๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์ต๋๋ค.- Zod๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ ํ๋์ ๋ํ ์ ํจ์ฑ ๊ฒ์ฌ ๊ท์น์ ์ง์ ํ๋
registrationSchema๋ฅผ ์ ์ํฉ๋๋ค. ์ฌ๊ธฐ์๋ ์ต์ ๊ธธ์ด ์๊ตฌ์ฌํญ ๋ฐ ์ด๋ฉ์ผ ํ์ ์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ํฌํจ๋ฉ๋๋ค. registerUser์๋ฒ ์ก์ ๋ด์์registrationSchema.parse(data)๋ฅผ ์ฌ์ฉํ์ฌ ํผ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฆํฉ๋๋ค.- ์ ํจ์ฑ ๊ฒ์ฌ์ ์คํจํ๋ฉด Zod๋
ZodError๋ฅผ ๋ฐ์์ํต๋๋ค. ์ด ์ค๋ฅ๋ฅผ ์ก์ ํด๋ผ์ด์ธํธ์ ์ ์ ํ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋ฐํํฉ๋๋ค.
์ ๊ทผ์ฑ ๊ณ ๋ ค์ฌํญ
ํผ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ๊ตฌํํ ๋ ์ ๊ทผ์ฑ์ ๊ณ ๋ คํ๋ ๊ฒ์ด ๋งค์ฐ ์ค์ํฉ๋๋ค. ์ฅ์ ๊ฐ ์๋ ์ฌ๋๋ค๋ ํผ์ ์ฌ์ฉํ ์ ์๋๋ก ํด์ผ ํฉ๋๋ค. ๋ค์์ ๋ช ๊ฐ์ง ์ฃผ์ ์ ๊ทผ์ฑ ๊ณ ๋ ค์ฌํญ์ ๋๋ค:
- ๋ช ํํ๊ณ ์ค๋ช ์ ์ธ ์ค๋ฅ ๋ฉ์์ง: ๋ฌด์์ด ์๋ชป๋์๊ณ ์ด๋ป๊ฒ ์์ ํด์ผ ํ๋์ง ์ค๋ช ํ๋ ๋ช ํํ๊ณ ์ค๋ช ์ ์ธ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ ๊ณตํ์ธ์. ARIA ์์ฑ์ ์ฌ์ฉํ์ฌ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ํด๋น ํผ ํ๋์ ์ฐ๊ฒฐํ์ธ์.
- ํค๋ณด๋ ๋ด๋น๊ฒ์ด์ : ๋ชจ๋ ํผ ์์๊ฐ ํค๋ณด๋๋ก ์ ๊ทผ ๊ฐ๋ฅํ์ง ํ์ธํ์ธ์. ์ฌ์ฉ์๋ Tab ํค๋ฅผ ์ฌ์ฉํ์ฌ ํผ์ ํ์ํ ์ ์์ด์ผ ํฉ๋๋ค.
- ์คํฌ๋ฆฐ ๋ฆฌ๋ ํธํ์ฑ: ์๋งจํฑ HTML๊ณผ ARIA ์์ฑ์ ์ฌ์ฉํ์ฌ ํผ์ด ์คํฌ๋ฆฐ ๋ฆฌ๋์ ํธํ๋๋๋ก ๋ง๋์ธ์. ์คํฌ๋ฆฐ ๋ฆฌ๋๋ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์๋ฆฌ๊ณ ์ฌ์ฉ์์๊ฒ ์๋ด๋ฅผ ์ ๊ณตํ ์ ์์ด์ผ ํฉ๋๋ค.
- ์ถฉ๋ถํ ๋๋น: ํผ ์์์ ํ ์คํธ์ ๋ฐฐ๊ฒฝ์ ๊ฐ์ ์ถฉ๋ถํ ๋๋น๊ฐ ์๋์ง ํ์ธํ์ธ์. ์ด๋ ํนํ ์ค๋ฅ ๋ฉ์์ง์ ์ค์ํฉ๋๋ค.
- ํผ ๋ ์ด๋ธ: `for` ์์ฑ์ ์ฌ์ฉํ์ฌ ๊ฐ ์ ๋ ฅ ํ๋์ ๋ ์ด๋ธ์ ์ฐ๊ฒฐํ์ฌ ๋ ์ด๋ธ๊ณผ ์ ๋ ฅ์ ์ฌ๋ฐ๋ฅด๊ฒ ์ฐ๊ฒฐํ์ธ์.
์์ : ์ ๊ทผ์ฑ์ ์ํ ARIA ์์ฑ ์ถ๊ฐํ๊ธฐ
// RegistrationForm.jsx
'use client';
import React from 'react';
import { experimental_useFormState as useFormState } from 'react-dom';
import { registerUser } from './server-actions';
function RegistrationForm() {
const [state, formAction] = useFormState(registerUser, { message: null });
return (
);
}
export default RegistrationForm;
์ค๋ช :
aria-invalid={!!state?.message}: ์ค๋ฅ ๋ฉ์์ง๊ฐ ์์ ๊ฒฝ์ฐaria-invalid์์ฑ์true๋ก ์ค์ ํ์ฌ ์ ๋ ฅ์ด ์ ํจํ์ง ์์์ ๋ํ๋ ๋๋ค.aria-describedby="name-error":aria-describedby์์ฑ์ ์ฌ์ฉํ์ฌ ์ ๋ ฅ์ ์ค๋ฅ ๋ฉ์์ง์ ์ฐ๊ฒฐํฉ๋๋ค.aria-live="polite": ์ค๋ฅ ๋ฉ์์ง๊ฐ ๋ํ๋ ๋ ์คํฌ๋ฆฐ ๋ฆฌ๋์๊ฒ ์ด๋ฅผ ์๋ฆฌ๋๋ก ์ง์ํฉ๋๋ค.
๊ตญ์ ํ(i18n) ๊ณ ๋ ค์ฌํญ
์ ์ธ๊ณ ์ฌ์ฉ์๋ฅผ ๋์์ผ๋ก ํ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฒฝ์ฐ ๊ตญ์ ํ(i18n)๋ ํ์์ ์ ๋๋ค. ํผ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ๊ตฌํํ ๋ ๋ค์ i18n ์ธก๋ฉด์ ๊ณ ๋ คํ์ธ์:
- ์ง์ญํ๋ ์ค๋ฅ ๋ฉ์์ง: ์ฌ์ฉ์๊ฐ ์ ํธํ๋ ์ธ์ด๋ก ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ ๊ณตํ์ธ์. i18n ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฒ์ญ์ ๊ด๋ฆฌํ์ธ์.
- ๋ ์ง ๋ฐ ์ซ์ ํ์: ์ฌ์ฉ์์ ๋ก์ผ์ผ์ ๋ฐ๋ผ ๋ ์ง ๋ฐ ์ซ์ ์ ๋ ฅ์ ๊ฒ์ฆํ์ธ์. ๋ ์ง ํ์๊ณผ ์ซ์ ๊ตฌ๋ถ ๊ธฐํธ๋ ๊ตญ๊ฐ๋ง๋ค ํฌ๊ฒ ๋ค๋ฆ ๋๋ค.
- ์ฃผ์ ์ ํจ์ฑ ๊ฒ์ฌ: ์ฌ์ฉ์์ ๊ตญ๊ฐ๋ณ ํน์ ์ฃผ์ ํ์ ๊ท์น์ ๋ฐ๋ผ ์ฃผ์๋ฅผ ๊ฒ์ฆํ์ธ์. ์ฃผ์ ํ์์ ์ ์ธ๊ณ์ ์ผ๋ก ๋งค์ฐ ๋ค์ํฉ๋๋ค.
- ์ค๋ฅธ์ชฝ์์ ์ผ์ชฝ์ผ๋ก(RTL) ์ง์: ์๋์ด ๋ฐ ํ๋ธ๋ฆฌ์ด์ ๊ฐ์ RTL ์ธ์ด์์ ํผ์ด ์ฌ๋ฐ๋ฅด๊ฒ ํ์๋๋์ง ํ์ธํ์ธ์.
์์ : ์ค๋ฅ ๋ฉ์์ง ์ง์ญํํ๊ธฐ
์ง์ญํ๋ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ํฌํจํ๋ ๋ฒ์ญ ํ์ผ(์: en.json, ko.json)์ด ์๋ค๊ณ ๊ฐ์ ํด ๋ณด๊ฒ ์ต๋๋ค.
// en.json
{
"nameRequired": "Name is required",
"invalidEmail": "Invalid email address",
"passwordTooShort": "Password must be at least 8 characters"
}
// ko.json
{
"nameRequired": "์ด๋ฆ์ ํ์์
๋๋ค",
"invalidEmail": "์๋ชป๋ ์ด๋ฉ์ผ ์ฃผ์์
๋๋ค",
"passwordTooShort": "๋น๋ฐ๋ฒํธ๋ 8์ ์ด์์ด์ด์ผ ํฉ๋๋ค"
}
// server-actions.js
"use server";
import { z } from 'zod';
// ์ฌ์ฉ์์ ๋ก์ผ์ผ์ ๊ฐ์ ธ์ค๋ ํจ์๊ฐ ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค
import { getLocale } from './i18n';
import translations from './translations';
const registrationSchema = z.object({
name: z.string().min(2, { message: "nameRequired" }),
email: z.string().email({ message: "invalidEmail" }),
password: z.string().min(8, { message: "passwordTooShort" }),
});
export async function registerUser(prevState, formData) {
const data = Object.fromEntries(formData);
const locale = getLocale(); // ์ฌ์ฉ์์ ๋ก์ผ์ผ ๊ฐ์ ธ์ค๊ธฐ
const t = translations[locale] || translations['en']; // ์์ด๋ก ํด๋ฐฑ
try {
const validatedData = registrationSchema.parse(data);
// ์ฌ์ฉ์ ๋ฑ๋ก ์๋ฎฌ๋ ์ด์
await new Promise(resolve => setTimeout(resolve, 1000)); // API ํธ์ถ ์๋ฎฌ๋ ์ด์
return { message: t['registrationSuccessful'] || 'Registration Successful!' };
} catch (error) {
if (error instanceof z.ZodError) {
return { message: t[error.errors[0].message] || 'Validation Error' };
} else {
return { message: t['unexpectedError'] || 'An unexpected error occurred.' };
}
}
}
์๋ฒ ์ธก ์ ํจ์ฑ ๊ฒ์ฌ์ ์ด์
ํด๋ผ์ด์ธํธ ์ธก ์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ์ฌ์ฉ์์๊ฒ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ ์ ๊ณตํ๋ ๋ฐ ์ค์ํ์ง๋ง, ์๋ฒ ์ธก ์ ํจ์ฑ ๊ฒ์ฌ๋ ๋ณด์ ๋ฐ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ์ ๋งค์ฐ ์ค์ํฉ๋๋ค. ๋ค์์ ์๋ฒ ์ธก ์ ํจ์ฑ ๊ฒ์ฌ์ ๋ช ๊ฐ์ง ์ฃผ์ ์ด์ ์ ๋๋ค:
- ๋ณด์: ์ ์์ ์ธ ์ฌ์ฉ์๊ฐ ํด๋ผ์ด์ธํธ ์ธก ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ฐํํ๊ณ ์ ํจํ์ง ์๊ฑฐ๋ ํด๋ก์ด ๋ฐ์ดํฐ๋ฅผ ์ ์ถํ๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค.
- ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ: ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ๋ ๋ฐ์ดํฐ๊ฐ ์ ํจํ๊ณ ์ผ๊ด์ฑ์ด ์๋๋ก ๋ณด์ฅํฉ๋๋ค.
- ๋น์ฆ๋์ค ๋ก์ง ๊ฐ์ : ํด๋ผ์ด์ธํธ ์ธก์์ ์ฝ๊ฒ ๊ตฌํํ ์ ์๋ ๋ณต์กํ ๋น์ฆ๋์ค ๊ท์น์ ๊ฐ์ ํ ์ ์์ต๋๋ค.
- ๊ท์ ์ค์: ๋ฐ์ดํฐ ๊ฐ์ธ์ ๋ณด ๋ณดํธ ๊ท์ ๋ฐ ๋ณด์ ํ์ค์ ์ค์ํ๋ ๋ฐ ๋์์ด ๋ฉ๋๋ค.
์ฑ๋ฅ ๊ณ ๋ ค์ฌํญ
experimental_useFormState๋ฅผ ๊ตฌํํ ๋ ์๋ฒ ์ก์
์ ์ฑ๋ฅ ์ํฅ์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค. ๊ณผ๋ํ๊ฑฐ๋ ๋นํจ์จ์ ์ธ ์๋ฒ ์ก์
์ ์ ํ๋ฆฌ์ผ์ด์
์ฑ๋ฅ์ ์ํฅ์ ์ค ์ ์์ต๋๋ค. ๋ค์์ ๋ช ๊ฐ์ง ์ฑ๋ฅ ์ต์ ํ ํ์
๋๋ค:
- ์๋ฒ ์ก์ ํธ์ถ ์ต์ํ: ๋ถํ์ํ๊ฒ ์๋ฒ ์ก์ ์ ํธ์ถํ์ง ๋ง์ธ์. ์ ๋ ฅ ์ด๋ฒคํธ๋ฅผ ๋๋ฐ์ด์คํ๊ฑฐ๋ ์ค๋กํํ์ฌ ์ ํจ์ฑ ๊ฒ์ฌ ์์ฒญ ๋น๋๋ฅผ ์ค์ด์ธ์.
- ์๋ฒ ์ก์ ๋ก์ง ์ต์ ํ: ์๋ฒ ์ก์ ๋ด์ ์ฝ๋๋ฅผ ์ต์ ํํ์ฌ ์คํ ์๊ฐ์ ์ต์ํํ์ธ์. ํจ์จ์ ์ธ ์๊ณ ๋ฆฌ์ฆ๊ณผ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ์ธ์.
- ์บ์ฑ: ์์ฃผ ์ ๊ทผํ๋ ๋ฐ์ดํฐ๋ฅผ ์บ์ํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ถํ๋ฅผ ์ค์ด์ธ์.
- ์ฝ๋ ์คํ๋ฆฌํ : ์ฝ๋ ์คํ๋ฆฌํ ์ ๊ตฌํํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ด๊ธฐ ๋ก๋ ์๊ฐ์ ์ค์ด์ธ์.
- CDN ์ฌ์ฉ: ์ฝํ ์ธ ์ ์ก ๋คํธ์ํฌ(CDN)์์ ์ ์ ์์ฐ์ ์ ๊ณตํ์ฌ ๋ก๋ฉ ์๋๋ฅผ ํฅ์์ํค์ธ์.
์ค์ ์ฌ์ฉ ์์
experimental_useFormState๊ฐ ํนํ ์ ์ฉํ ์ ์๋ ๋ช ๊ฐ์ง ์ค์ ์๋๋ฆฌ์ค๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค:
- ์ ์์๊ฑฐ๋ ๊ฒฐ์ ํผ: ์ ์์๊ฑฐ๋ ๊ฒฐ์ ๊ณผ์ ์์ ๋ฐฐ์ก ์ฃผ์, ๊ฒฐ์ ์ ๋ณด ๋ฐ ์ฒญ๊ตฌ ์ธ๋ถ ์ ๋ณด๋ฅผ ๊ฒ์ฆํฉ๋๋ค.
- ์ฌ์ฉ์ ํ๋กํ ๊ด๋ฆฌ: ์ด๋ฆ, ์ด๋ฉ์ผ ์ฃผ์, ์ ํ๋ฒํธ์ ๊ฐ์ ์ฌ์ฉ์ ํ๋กํ ์ ๋ณด๋ฅผ ๊ฒ์ฆํฉ๋๋ค.
- ์ฝํ ์ธ ๊ด๋ฆฌ ์์คํ (CMS): ๊ธฐ์ฌ, ๋ธ๋ก๊ทธ ๊ฒ์๋ฌผ, ์ ํ ์ค๋ช ๊ณผ ๊ฐ์ ์ฝํ ์ธ ํญ๋ชฉ์ ๊ฒ์ฆํฉ๋๋ค.
- ๊ธ์ต ์ ํ๋ฆฌ์ผ์ด์ : ๊ฑฐ๋ ๊ธ์ก, ๊ณ์ข ๋ฒํธ, ๋ผ์ฐํ ๋ฒํธ์ ๊ฐ์ ๊ธ์ต ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฆํฉ๋๋ค.
- ์๋ฃ ์ ํ๋ฆฌ์ผ์ด์ : ์๋ฃ ๊ธฐ๋ก, ์๋ ๋ฅด๊ธฐ, ์ฝ๋ฌผ๊ณผ ๊ฐ์ ํ์ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฆํฉ๋๋ค.
๋ชจ๋ฒ ์ฌ๋ก
experimental_useFormState๋ฅผ ์ต๋ํ ํ์ฉํ๋ ค๋ฉด ๋ค์ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ฐ๋ฅด์ธ์:
- ์๋ฒ ์ก์ ์ ์๊ณ ์ง์ค์ ์ผ๋ก ์ ์ง: ํน์ ์์ ์ ์ํํ๋๋ก ์๋ฒ ์ก์ ์ ์ค๊ณํ์ธ์. ์ง๋์น๊ฒ ๋ณต์กํ ์๋ฒ ์ก์ ์ ๋ง๋ค์ง ๋ง์ธ์.
- ์๋ฏธ ์๋ ์ํ ์ ๋ฐ์ดํธ ์ฌ์ฉ: ์ค๋ฅ ๋ฉ์์ง๋ ์ฑ๊ณต ํ์๊ธฐ์ ๊ฐ์ ์๋ฏธ ์๋ ์ ๋ณด๋ก ํผ ์ํ๋ฅผ ์ ๋ฐ์ดํธํ์ธ์.
- ๋ช ํํ ์ฌ์ฉ์ ํผ๋๋ฐฑ ์ ๊ณต: ํผ ์ํ์ ๋ฐ๋ผ ์ฌ์ฉ์์๊ฒ ๋ช ํํ๊ณ ์ ์ตํ ํผ๋๋ฐฑ์ ํ์ํ์ธ์.
- ์ฒ ์ ํ ํ ์คํธ: ํผ์ด ์ฌ๋ฐ๋ฅด๊ฒ ์๋ํ๊ณ ๋ชจ๋ ๊ฐ๋ฅํ ์๋๋ฆฌ์ค๋ฅผ ์ฒ๋ฆฌํ๋์ง ์ฒ ์ ํ ํ ์คํธํ์ธ์. ์ฌ๊ธฐ์๋ ๋จ์ ํ ์คํธ, ํตํฉ ํ ์คํธ, ์๋ํฌ์๋ ํ ์คํธ๊ฐ ํฌํจ๋ฉ๋๋ค.
- ์ต์ ์ ๋ณด ์ ์ง: React ๋ฐ
experimental_useFormState์ ๋ํ ์ต์ ์ ๋ฐ์ดํธ์ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๊ณ์ ํ์ธํ์ธ์.
๊ฒฐ๋ก
React์ experimental_useFormState ํ
์ ํนํ ์๋ฒ ์ก์
๊ณผ ๊ฒฐํฉ๋ ๋ ํผ ์ํ ๋ฐ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ๊ด๋ฆฌํ๋ ๊ฐ๋ ฅํ๊ณ ํจ์จ์ ์ธ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค. ์ด ํ
์ ํ์ฉํ์ฌ ํผ ์ฒ๋ฆฌ ๋ก์ง์ ๊ฐ์ํํ๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๋ฉฐ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ์ ๋ณด์ฅํ ์ ์์ต๋๋ค. ํผ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ๊ตฌํํ ๋ ์ ๊ทผ์ฑ, ๊ตญ์ ํ, ์ฑ๋ฅ์ ๊ณ ๋ คํ๋ ๊ฒ์ ์์ง ๋ง์ธ์. ์ด ๊ฐ์ด๋์ ์ค๋ช
๋ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ฐ๋ฅด๋ฉด ์น ์ ํ๋ฆฌ์ผ์ด์
์ ํฅ์์ํค๋ ๊ฒฌ๊ณ ํ๊ณ ์ฌ์ฉ์ ์นํ์ ์ธ ํผ์ ๋ง๋ค ์ ์์ต๋๋ค.
experimental_useFormState๊ฐ ๊ณ์ ๋ฐ์ ํจ์ ๋ฐ๋ผ ์ต์ ์
๋ฐ์ดํธ์ ๋ชจ๋ฒ ์ฌ๋ก์ ๋ํ ์ ๋ณด๋ฅผ ์ป๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ์ด ํ์ ์ ์ธ ๊ธฐ๋ฅ์ ๋ฐ์๋ค์ฌ ํผ ์ ํจ์ฑ ๊ฒ์ฌ ์ ๋ต์ ์๋ก์ด ์ฐจ์์ผ๋ก ๋์ด์ฌ๋ฆฌ์ธ์.